package com.xxx.springboot.controller;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.CacheWriter;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import com.xxx.springboot.pojo.entity.User;
import com.xxx.springboot.service.UserService;

import lombok.NonNull;

/**
 * Caffeine cache 测试
 */
@Controller
@RequestMapping(value = "/", produces = "application/json;charset=utf-8")
public class CaffeineCacheController extends BaseController {
	
	@Autowired
	private UserService userService;
	
	// Caffeine cache 原生API用法
	LoadingCache<String, User> caffeineCache = Caffeine.newBuilder()
				// 初始化容量（默认16）
				.initialCapacity(1)
				// 最大个数限制
				.maximumSize(256L)
				
				// 多长时间内没有读/写操作就会过期（60秒内没有被读/写就会移除key）
				.expireAfterAccess(60, TimeUnit.SECONDS)
				// 多长时间内没有写操作就会过期（10秒内没有被创建/更新就会移除key）
				// 注意：当同时存在expireAfterAccess和expireAfterWrite时，expireAfterWrite会覆盖expireAfterAccess设置
				.expireAfterWrite(10, TimeUnit.SECONDS)
				// 多长时间内没有写操作，则经过指定时间后再次访问时就会刷新缓存，在新值到来之前，始终返回旧值（60秒内没有发生创建/更新操作，经过60秒后如果再次访问会刷新缓存，在新值没有到来之前返回旧值）
				// 注意：开启refreshAfterWrite后，必须要我们去实现一个CacheLoader，否则会启动异常。
				.refreshAfterWrite(60, TimeUnit.SECONDS)
				// 打开统计功能，例如命中率等
				.recordStats()
				
//				// 还可以自定义过期时间（使不同的key可以动态的得到不同的过期时间，不能和expireAfterAccess、expireAfterWrite同时存在）
//				.expireAfter(new Expiry<String, User>(){
//
//					// 返回创建后的过期时间
//					@Override
//					public long expireAfterCreate(String key, User value, long currentTime) {
//						// TODO Auto-generated method stub
//						return 0;
//					}
//
//					// 返回更新后的过期时间
//					@Override
//					public long expireAfterUpdate(String key, User value, long currentTime, long currentDuration) {
//						// TODO Auto-generated method stub
//						return 0;
//					}
//
//					// 返回读取后的过期时间
//					@Override
//					public long expireAfterRead(String key, User value, long currentTime, long currentDuration) {
//						// TODO Auto-generated method stub
//						return 0;
//					}
//					
//				})
				
				// cache对写缓存的回调通知
				.writer(new CacheWriter<String, User>() {
					// key写入（新增/更新）
					@Override
					public void write(@NonNull String key, @NonNull User value) {
						System.out.println("CacheWriter write. key:"+ key + " value:"+ value);
					}
					
					// key删除
					@Override
					public void delete(@NonNull String key, @Nullable User value, @NonNull RemovalCause cause) {
						System.out.println("CacheWriter delete. key:" + key+", cause:"+ cause);
					}
				})
				
				// 监听剔除事件
				.removalListener((RemovalListener<String, User>) (key, value, cause) -> {
    				System.out.println("key:" + key + ", value:" + value + ", cause:" + cause.toString());
				})
				
				// 使用CacheLoader创建一个LoadingCache对象
				.build(new CacheLoader<String, User>() {
					// 同步加载数据
					@Nullable
					@Override
					public User load(@NonNull String key) {
						System.out.println("value does not exist in cache. load from DB. key:"+ key);
						return userService.getById(key);
					}
	
					// 异步加载数据
					@Nullable
					@Override
					public User reload(@NonNull String key, @NonNull User oldValue) {
						System.out.println("enter method reload(). oldValue" + oldValue.toString());
						System.out.println("value does not exist in cache. reload from DB. key:"+ key);
						return userService.getById(key);
					}
				});

	/**
     * 测试caffeine原生API：get
     * @param userId
     * @return
     * @throws ExecutionException 
     */
    @RequestMapping("/testGetCaffeineCache")
    @ResponseBody
    public User testGetCaffeineCache(String userId) throws ExecutionException {
        System.out.println("--------------------enter method testGetCaffeineCache----------------------");
        User user = caffeineCache.get(userId);
        System.out.println("user:"+(user==null ? "null" : user.toString()));
        System.out.println("--------------------exit method testGetCaffeineCache----------------------");
        return user;
    }
    
    /**
     * 测试caffeine原生API： refresh
     * @param userId
     * @return
     * @throws ExecutionException 
     */
    @RequestMapping("/testRefreshCaffeineCache")
    @ResponseBody
    public User testRefreshCaffeineCache(String userId, String userName) throws ExecutionException {
        System.out.println("------------------enter method testRefreshCaffeineCache------------------");
        
        // 先从cache里获取
        User user1 = caffeineCache.get(userId);
        System.out.println("user1:"+(user1==null ? "null" : user1.toString()));
        
        // 修改姓名后，更新到DB中，同时刷新缓存
        if(!StringUtils.isEmpty(userName)) {
        	user1.setRealName(userName);
        }
        boolean result = userService.updateById(user1); // 更新到DB
        if(result) { // 更新DB成功
        	caffeineCache.refresh(userId); // 主动刷新缓存：调用reload()方法（异步）
        }
        
        // 再次从缓存中获取
        User user2 = caffeineCache.get(userId);
        System.out.println("user2:"+(user2==null ? "null" : user2.toString()));
        
        System.out.println("------------------exit method testRefreshCaffeineCache------------------");
        return user2;
    }
    
    /**
     * 测试springboot集成caffeine
     * @param userId
     * @return
     * @throws ExecutionException 
     */
    @RequestMapping("/testSpringbootCaffeineCache")
    @ResponseBody
    public User testSpringbootCaffeineCache(String userName) throws ExecutionException {
        System.out.println("--------------------enter method testSpringbootCaffeineCache----------------------");
        User user = userService.getByName(userName);
        System.out.println("user:"+(user==null ? "null" : user.toString()));
        System.out.println("--------------------exit method testSpringbootCaffeineCache----------------------");
        return user;
    }
    
    /**
     * 获取caffeine cache统计信息
     * @return
     * @throws ExecutionException
     */
    @RequestMapping("/testCaffeineCacheRecordStats")
    @ResponseBody
    public String testCaffeineCacheRecordStats() throws ExecutionException {
        System.out.println("--------------------enter method testCaffeineCacheRecordStats----------------------");
        CacheStats stats = caffeineCache.stats();
        System.out.println("stats:"+(stats==null ? "null" : stats.toString()));
        System.out.println("--------------------exit method testCaffeineCacheRecordStats----------------------");
        return stats==null ? null : stats.toString();
    }
    
}
